# -*- coding: utf-8; -*-

"""Code for the main GUI window of Siocwave."""

import sys

import wx
import wx.html

import meta
from meta import __version__
import model
import prefsdlg


class LoadMoreMenu(wx.Menu):
    """A context menu for selective loading of additional data."""
    def __init__(self, parent, res):
        """Create a "load more" menu.
        
        Create a "load more" context menu in main window `parent`, for
        resource `res`.
        """
        wx.Menu.__init__(self)
        self.parent = parent
        self.loaded = False
        
        for url, desc in res.list_more():
            desc = desc.replace(u'&', u'&&')    # Disable mnemonics
            item = wx.MenuItem(self, id=-1, text=desc, help=url)
            self.AppendItem(item)
            self.Bind(wx.EVT_MENU, self.make_handler(res, url),
                      id=item.GetId())
    
    def make_handler(self, res, url):
        def _handler(event):
            res.load_more(url)
            self.loaded = True
        return _handler


class MainWindow(wx.Frame):
    """The main window."""
    def __init__(self, graph_mgr, config_mgr):
        wx.Frame.__init__(self, parent=None, title=u'Siocwave',
                          size=(800, 600))
        
        menubar = wx.MenuBar()
        data_menu = wx.Menu()
        load_item = data_menu.Append(-1, text=u'&Load...',
                                     help=u'Load RDF data into the graph')
        self.recent_menu = wx.Menu()
        data_menu.AppendMenu(-1, text=u'Load &Recent',
                             submenu=self.recent_menu)
        infer_item = data_menu.Append(-1, text=u'&Infer',
            help=u'Perform RDF Schema inference and add materialized triples '
                 u'to the graph')
        clear_item = data_menu.Append(-1, text=u'&Clear',
                                      help=u'Clear the working graph')
        data_menu.AppendSeparator()
        prefs_item = data_menu.Append(-1, text=u'&Preferences...',
                                      help=u'Set Siocwave preferences')
        data_menu.AppendSeparator()
        data_menu.Append(wx.ID_EXIT)
        menubar.Append(data_menu, u'&Data')
        help_menu = wx.Menu()
        about_item = help_menu.Append(wx.ID_ABOUT)
        menubar.Append(help_menu, u'&Help')
        self.SetMenuBar(menubar)
        
        splitter = wx.SplitterWindow(self)
        self.tree = wx.TreeCtrl(splitter,
                                style=(wx.TR_HAS_BUTTONS | wx.TR_NO_LINES |
                                       wx.TR_FULL_ROW_HIGHLIGHT))
        self.resource_pane = wx.Panel(splitter)
        splitter.SplitVertically(self.tree, self.resource_pane)
        
        self.statusbar = self.CreateStatusBar()
        
        self.graph_mgr = graph_mgr
        self.graph_mgr.log = self.log
        
        self.config_mgr = config_mgr
        
        self.Bind(wx.EVT_MENU, self.on_invoke_load, id=load_item.GetId())
        self.Bind(wx.EVT_MENU, self.on_invoke_infer, id=infer_item.GetId())
        self.Bind(wx.EVT_MENU, self.on_invoke_clear, id=clear_item.GetId())
        self.Bind(wx.EVT_MENU, self.on_invoke_prefs, id=prefs_item.GetId())
        self.Bind(wx.EVT_MENU, self.on_invoke_exit, id=wx.ID_EXIT)
        self.Bind(wx.EVT_MENU, self.on_invoke_about, id=about_item.GetId())
        
        self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_tree_select)
        
        self.Centre()
        self.Show()
        
        self.config_mgr.load()
        self.rebuild_recent()
        self.graph_mgr.preload()
        self.redisplay()
    
    def log(self, s):
        self.statusbar.SetStatusText(s)
        sys.stderr.write(s + u'\n')
    
    def rebuild_recent(self):
        """Rebuild the recently loaded menu."""
        def make_handler(url):
            def _handler(event):
                self.do_load(url)
            return _handler
        for item in self.recent_menu.GetMenuItems():
            self.recent_menu.DestroyItem(item)
        for recent_url in self.config_mgr.recent:
            recent_label = recent_url
            if len(recent_label) > 63:
                recent_label = recent_label[:30] + u'...' + recent_label[-30:]
            recent_label = recent_label.replace(u'&', u'&&')
            item = self.recent_menu.Append(-1, text=recent_label)
            self.Bind(wx.EVT_MENU, make_handler(recent_url), id=item.GetId())
    
    def find_tree_item(self, data, start_at=None):
        """Return the tree item ID which is associated with `data`, or None."""
        if start_at is None:
            item = self.tree.GetRootItem()
        else:
            item, cookie = self.tree.GetFirstChild(start_at)
        while (item is not None) and item.IsOk():
            if self.tree.GetItemPyData(item) == data:
                return item
            if self.tree.ItemHasChildren(item):
                below = self.find_tree_item(data, start_at=item)
                if below is not None:
                    return below
            item = self.tree.GetNextSibling(item)
    
    def select_item(self, data):
        """Select the tree item that is associated with `data`."""
        item = self.find_tree_item(data)
        if item is not None:
            self.tree.SelectItem(item, select=True)
            self.tree.EnsureVisible(item)
    
    def do_load(self, url):
        """Perform the loading of the `url`, as if via Data -> Load."""
        self.graph_mgr.load(url)
        self.redisplay()
        self.config_mgr.add_recent(url)
        self.rebuild_recent()
    
    def on_invoke_load(self, event):
        dlg = wx.TextEntryDialog(self, message=u'URL of the resource to load:',
                                 caption=u'Load data into graph',
                                 defaultValue=u'')
        dlg.ShowModal()
        url = dlg.GetValue()
        if url != u'':
            if not meta.is_ascii(url):
                meta.show_ascii_error(self)
            else:
                self.do_load(url)
    
    def on_invoke_infer(self, event):
        self.graph_mgr.infer()
        self.redisplay()
    
    def on_invoke_clear(self, event):
        self.graph_mgr.clear()
        self.graph_mgr.preload()
        self.redisplay()
    
    def on_invoke_prefs(self, event):
        prefsdlg.PreferencesDialog(self, self.config_mgr)
    
    def on_invoke_res_load_more(self, event):
        res = self.tree.GetItemPyData(self.tree.GetSelection())
        if res is not None:
            res.load_more()
            self.redisplay()
            self.select_item(res)
    
    def on_invoke_creator_load_more(self, event):
        res = self.tree.GetItemPyData(self.tree.GetSelection())
        if res is not None:
            res.creator.load_more()
            self.redisplay()
            self.select_item(res)
    
    def on_invoke_about(self, event):
        info = wx.AboutDialogInfo()
        info.SetName(u'Siocwave')
        info.SetVersion(__version__)
        info.SetDescription(u'Flexible SIOC browser')
        info.SetWebSite(u'http://code.google.com/p/siocwave/')
        info.AddDeveloper(u'Vasiliy Faronov <vfaronov@gmail.com>')
        info.AddDeveloper(u'RDFS inference code by Ivan Herman <ivan@w3.org>')
        wx.AboutBox(info)
    
    def on_invoke_exit(self, event):
        self.Close()
    
    def on_tree_select(self, tree_event):
        item = tree_event.GetItem()
        if item.IsOk():
            res = self.tree.GetItemPyData(tree_event.GetItem())
            if res is not None:
                self.fill_resource_pane(res)
    
    def clear_resource_pane(self):
        """Clear the resource pane (to the right of the splitter)."""
        for win in self.resource_pane.GetChildren():
            win.Destroy()
    
    def fill_resource_pane(self, res):
        """Rebuild the resource pane for resource `res`."""
        def make_popup_handler(btn, menu):
            """Make a context menu event handler."""
            def _handler(event):
                btn.PopupMenu(menu)
                if menu.loaded:
                    # The user has selected something in the menu, so we need
                    # to redisplay. (If we do this in the menu event handler
                    # itself, wxWidgets segfaults.)
                    self.redisplay()
                    self.select_item(res)
            return _handler
        
        # Determine the background colour to use for selectable fields.
        # I could only get reliable info from a StaticText.
        dummy = wx.StaticText(self.resource_pane, label=u'')
        bgcolor = dummy.GetBackgroundColour()
        dummy.Destroy()
        
        def make_field(text):
            """Make and return a selectable (but not editable) field."""
            field = wx.TextCtrl(self.resource_pane,
                                style=(wx.NO_BORDER | wx.TE_READONLY))
            field.SetValue(text)
            field.SetBackgroundColour(bgcolor)
            return field
                
        self.Freeze()
        self.clear_resource_pane()
        vbox = wx.BoxSizer(wx.VERTICAL)
        
        has_creator_button = False
        
        # Basic resource information (label, URI and a "Load more" button).
        basic_hbox = wx.BoxSizer(wx.HORIZONTAL)
        basic_vbox = wx.BoxSizer(wx.VERTICAL)
        label = make_field(res.label)
        label_font = self.GetFont()
        label_font.SetWeight(wx.BOLD)
        label.SetFont(label_font)
        uri_label = make_field(unicode(res.ref))
        basic_vbox.Add(label, flag=wx.EXPAND)
        basic_vbox.Add(uri_label, flag=wx.EXPAND)
        load_more_btn = wx.Button(self.resource_pane, label=u'Load more')
        load_more_btn.Enable(len(res.list_more()) > 0)
        load_more_btn.Bind(wx.EVT_CONTEXT_MENU,
                           make_popup_handler(load_more_btn,
                                              LoadMoreMenu(self, res)))
        basic_hbox.Add(basic_vbox, proportion=1, flag=wx.EXPAND)
        basic_hbox.Add(load_more_btn, flag=(wx.ALIGN_RIGHT | wx.LEFT),
                       border=5)
        vbox.Add(basic_hbox, flag=(wx.EXPAND | wx.ALL), border=5)
        
        # Item content.
        if isinstance(res, model.Item):
            separator1 = wx.StaticLine(self.resource_pane)
            if res.created is not None:
                created_label = make_field(u'Created: %s' % res.created)
            has_content = False
            
            if res.encoded_content is not None:
                # We want to make an HTML window
                has_content = True
                content_view = wx.html.HtmlWindow(self.resource_pane)
                page = u'<html><body>%s</body></html>' % \
                       res.encoded_content
                content_view.SetPage(page)
            elif res.plain_content is not None:
                has_content = True
                content_view = wx.TextCtrl(self.resource_pane,
                    style=(wx.NO_BORDER | wx.TE_MULTILINE | wx.TE_READONLY))
                content_view.SetValue(res.plain_content)
            vbox.Add(separator1, flag=(wx.EXPAND | wx.ALL), border=5)
            if res.created is not None:
                vbox.Add(created_label, flag=(wx.EXPAND | wx.ALL), border=5)
            if has_content:
                vbox.Add(content_view, proportion=1,
                         flag=(wx.EXPAND | wx.ALL), border=5)
            
            # Figure out the creator of the item.
            creator = res.creator
            if isinstance(creator, model.Agent):
                # We need to show a full-fledged Creator Pane.
                separator2 = wx.StaticLine(self.resource_pane)
                creator_hbox = wx.BoxSizer(wx.HORIZONTAL)
                creator_vbox = wx.BoxSizer(wx.VERTICAL)
                creator_label = make_field(u'Creator: %s' % creator.label)
                creator_uri_label = make_field(unicode(creator.ref))
                creator_vbox.Add(creator_label, flag=wx.EXPAND)
                creator_vbox.Add(creator_uri_label, flag=wx.EXPAND)
                creator_load_more_btn = wx.Button(self.resource_pane,
                                                  label=u'Load more')
                has_creator_button = True
                creator_load_more_btn.Enable(len(res.creator.list_more()) > 0)
                creator_load_more_btn.Bind(wx.EVT_CONTEXT_MENU,
                    make_popup_handler(creator_load_more_btn,
                                       LoadMoreMenu(self, res.creator)))
                creator_hbox.Add(creator_vbox, proportion=1, flag=wx.EXPAND)
                creator_hbox.Add(creator_load_more_btn,
                                 flag=(wx.ALIGN_RIGHT | wx.LEFT), border=5)
                vbox.Add(separator2, flag=(wx.EXPAND | wx.ALL), border=5)
                vbox.Add(creator_hbox, flag=(wx.EXPAND | wx.ALL), border=5)
            elif isinstance(creator, unicode):
                # We only need to show the creator's name.
                separator2 = wx.StaticLine(self.resource_pane)
                creator_label = make_field(u'Creator: %s' % creator)
                vbox.Add(separator2, flag=(wx.EXPAND | wx.ALL), border=5)
                vbox.Add(creator_label, flag=(wx.EXPAND | wx.ALL), border=5)
        
        self.resource_pane.SetSizer(vbox)
        self.resource_pane.Layout()
        self.Thaw()
        
        # Finally, hook up the event handlers.
        load_more_btn.Bind(wx.EVT_BUTTON, self.on_invoke_res_load_more)
        if has_creator_button:
            creator_load_more_btn.Bind(wx.EVT_BUTTON,
                                       self.on_invoke_creator_load_more)
    
    def redisplay(self):
        """Rebuilds the display of everything based on the current state."""
        def add_subtree(res, root):
            """Recursively add a sub-tree for `res` under `root`."""
            subtree = self.tree.AppendItem(parent=root, text=res.label)
            self.tree.SetItemPyData(subtree, res)
            if hasattr(res, 'list_children'):
                for ch in res.list_children():
                    add_subtree(ch, subtree)
        self.Freeze()
        self.tree.DeleteAllItems()
        root = self.tree.AddRoot(text=u'SIOC hierarchy')
        for container in self.graph_mgr.list_toplevel_containers():
            add_subtree(container, root)
        toplevel_items = self.graph_mgr.list_toplevel_items()
        if len(toplevel_items) > 0:
            toplevel_items_subtree = \
                self.tree.AppendItem(parent=root,
                                     text=u'(items without container)')
            for item in toplevel_items:
                add_subtree(item, toplevel_items_subtree)
        self.tree.ExpandAll()
        self.tree.UnselectAll()
        self.clear_resource_pane()
        self.Thaw()
